﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestXNA3.Components;
using Microsoft.Xna.Framework;
using System.IO;
using System.Reflection;
using TestXNA3.Render;
using Microsoft.Xna.Framework.Graphics;
using TestXNA3.Generic;
using System.Runtime.Serialization;

namespace TestXNA3.Actors
{
    [Serializable]
    public class Actor : ISerializable
    {
        // Actor type
        [System.ComponentModel.Category("Actor")]
        public Type _Type
        {
            get { return GetType(); }
        }

        // Scene
        private Scene _scene;

        [System.ComponentModel.Browsable(false)]
        public Scene Scene
        {
            get { return _scene; }
            set
            {
                _scene = value;
                foreach (Actor a in Childs)
                {
                    a.Scene = _scene;
                }
            }
        }

        // Component list
        public List<Component> _components;
        [System.ComponentModel.Category("Actor")]
        public List<Component> Components
        {
            get { return _components; }
            set { _components = value; }
        }

        // Child actor list
        public List<Actor> Childs;

        // Actor parent
        public Actor Parent;
        public bool BasedOnParentAxis = true;

        // Bounds
        public bool DrawBoundingBox;
        public BoundingBox BoundingBox;
        private Vector3 _boundingBoxSize;

        [System.ComponentModel.Browsable(false)]
        public Vector3 BoundingBoxSize
        {
            get { return _boundingBoxSize; }
            set { _boundingBoxSize = value; UpdateBoundingBox(); }
        }

        // Actor orientation
        private Matrix _axis;
        private bool _notifyAxisChanged = false;
        private bool _axisBuilt = false;

        [System.ComponentModel.Browsable(false)]
        public Matrix Axis
        {
            get { if (!_axisBuilt) updateAxis(); return _axis; }
            set { _axis = value; if (!_updatingComponents) _notifyAxisChanged = true; }
        }
        private Matrix _worldAxis;

        // Rotation
        private float _yaw;
        [System.ComponentModel.Browsable(false)]
        public float Yaw
        {
            get { return _yaw; }
            set { _axisBuilt = false; _yaw = value; }
        }

        private float _pitch;
        [System.ComponentModel.Browsable(false)]
        public float Pitch
        {
            get { return _pitch; }
            set { _axisBuilt = false; _pitch = value; }
        }

        private float _roll;
        [System.ComponentModel.Browsable(false)]
        public float Roll
        {
            get { return _roll; }
            set { _axisBuilt = false; _roll = value; }
        }

        [System.ComponentModel.Category("Actor")]
        public Vector3 Rotation
        {
            get { return new Vector3(Yaw, Pitch, Roll); }
            set { Yaw = value.X; Pitch = value.Y; Roll = value.Z; }
        }

        // Scale
        public Vector3 _scale;
        [System.ComponentModel.Category("Actor")]
        public Vector3 Scale
        {
            get { return _scale; }
            set
            {
                _axisBuilt = false;
                _scale = value;
                UpdateBoundingBox();
                if (!_updatingComponents) notifyScaleChanged();
            }
        }

        // Actor location
        [System.ComponentModel.Category("Actor")]
        public Vector3 Location
        {
            get { return _axis.Translation; }
            set { _axisBuilt = false; _axis.Translation = value; }
        }

        // Actor rotation
        [System.ComponentModel.Browsable(false)]
        public Vector3 Forward
        {
            get { return _axis.Forward; }
        }
        [System.ComponentModel.Browsable(false)]
        public Vector3 Right
        {
            get { return _axis.Right; }
        }
        [System.ComponentModel.Browsable(false)]
        public Vector3 Up
        {
            get { return _axis.Up; }
        }

        private bool _updatingChilds = false;
        private bool _updatingComponents = false;
        private List<Actor> _delayedChildsToRemove;
        private List<Component> _delayedComponentsToRemove;

        protected bool _saveable = false;
        [System.ComponentModel.Browsable(false)]
        public bool Saveable
        {
            get { return _saveable; }
        }

        protected bool _placable = false;
        [System.ComponentModel.Browsable(false)]
        public bool Placable
        {
            get { return _placable; }
        }

        // Constructor
        public Actor()
        {
            Components = new List<Component>();
            Childs = new List<Actor>();

            _delayedChildsToRemove = new List<Actor>();
            _delayedComponentsToRemove = new List<Component>();

            Parent = null;

            _axis = Matrix.CreateTranslation(new Vector3(0.0f, 0.0f, 0.0f));
            _worldAxis = _axis;

            _scale = Vector3.One;
            _boundingBoxSize = new Vector3(1.0f, 1.0f, 1.0f);
            BoundingBox = new BoundingBox(new Vector3(-0.5f, -0.5f, -0.5f), new Vector3(0.5f, 0.5f, 0.5f));
            UpdateBoundingBox();
        }

        // Create actor from serialization
        public Actor(BinaryReader reader)
        {
            Load(reader);
        }

        // Destroy actor
        public virtual void Destroy()
        {
            _updatingComponents = true;
            foreach (Component c in _components)
            {
                DetachComponent(c);
            }
            _updatingComponents = false;

            _updatingChilds = true;
            foreach (Actor c in Childs)
            {
                DetachActor(c);
            }
            _updatingChilds = false;

            postUpdate(0f);

            Parent.DetachActor(this);
            Parent = null;
        }

        // Attach component, component can be attached only to one actor
        public void AttachComponent(Component C)
        {
            C.Parent = this;
            Components.Add(C);
        }

        // Detach component, can be delayed
        public void DetachComponent(Component C)
        {
            if (_updatingComponents)
            {
                _delayedComponentsToRemove.Add(C);
                return;
            }
            C.Parent = null;
            Components.Remove(C);
        }

        // Attach actor
        public virtual void AttachActor(Actor A)
        {
            if (A.Parent != null)
            {
                A.Parent.DetachActor(A);
            }
            A.Parent = this;
            Childs.Add(A);
        }

        // Detach actor, can be delayed, but with new parent
        public virtual void DetachActor(Actor A)
        {
            if (_updatingChilds)
            {
                _delayedChildsToRemove.Add(A);
                return;
            }
            Childs.Remove(A);
        }

        // Update logic
        public virtual void Update(float dt)
        {
            preUpdate(dt);

            _updatingChilds = true;
            foreach (Actor a in Childs)
            {
                a.Update(dt);
            }
            _updatingChilds = false;

            if (DrawBoundingBox)
            {
                SceneRenderer.DrawDebugBox(BoundingBox, Color.Violet, false, false);
            }

            postUpdate(dt);
        }

        private void updateAxis()
        {
            _axis = Matrix.CreateScale(_scale) * Matrix.CreateFromYawPitchRoll(Yaw * 3.1416f / 180.0f, Pitch * 3.1416f / 180.0f, Roll * 3.1416f / 180.0f) * Matrix.CreateTranslation(_axis.Translation);
            _worldAxis = (Parent != null && BasedOnParentAxis) ? _axis * Parent._axis : _axis;
            UpdateBoundingBox();

            _axisBuilt = true;
        }

        // Calls before update childs
        protected virtual void preUpdate(float dt)
        {
            if (!_axisBuilt)
            {
                updateAxis();
            }

            if (_notifyAxisChanged)
            {
                notifyAxisChanged();
            }

            _updatingComponents = true;
            foreach (Component c in Components)
            {
                if (c.UpdateGroup == Component.EUpdateGroup.PreUpdate)
                {
                    c.Update(dt);
                }
            }
            _updatingComponents = false;
        }

        // Called, when childs are updated
        protected virtual void postUpdate(float dt)
        {
            _updatingComponents = true;
            foreach (Component c in Components)
            {
                if (c.UpdateGroup == Component.EUpdateGroup.PostUpdate)
                {
                    c.Update(dt);
                }
            }
            _updatingComponents = false;

            // Process delayed operations
            foreach (Component c in _delayedComponentsToRemove)
            {
                DetachComponent(c);
            }
            _delayedComponentsToRemove.Clear();

            foreach (Actor a in _delayedChildsToRemove)
            {
                DetachActor(a);
            }
            _delayedChildsToRemove.Clear();
        }

        // Save/load
        public virtual void Save(BinaryWriter writer)
        {
            if (!_saveable)
                return;

            writer.Write(Location.X);
            writer.Write(Location.Y);
            writer.Write(Location.Z);
            writer.Write(Yaw);
            writer.Write(Pitch);
            writer.Write(Roll);
            writer.Write(Scale.X);
            writer.Write(Scale.Y);
            writer.Write(Scale.Z);

            long componentsCount = 0;

            foreach (Component c in Components)
            {
                if (c.Saveable)
                {
                    componentsCount++;
                }
            }

            writer.Write(componentsCount);
            foreach (Component c in Components)
            {
                if (c.Saveable)
                {
                    Type componentType = c.GetType();
                    writer.Write(componentType.FullName);
                    c.Save(writer);
                }
            }
        }

        public virtual void Load(BinaryReader reader)
        {
            if (!_saveable)
                return;

            Vector3 location = Vector3.Zero;
            location.X = reader.ReadSingle();
            location.Y = reader.ReadSingle();
            location.Z = reader.ReadSingle();
            Location = location;
            Yaw = reader.ReadSingle();
            Pitch = reader.ReadSingle();
            Roll = reader.ReadSingle();

            Vector3 scale = Vector3.One;
            scale.X = reader.ReadSingle();
            scale.Y = reader.ReadSingle();
            scale.Z = reader.ReadSingle();
            Scale = scale;

            long componentsCount = reader.ReadInt64();
            for (int i = 0; i < componentsCount; i++)
            {
                string assemblyQualifiedName = reader.ReadString();
                object o = Assembly.GetExecutingAssembly().CreateInstance(assemblyQualifiedName);
                if (o is Component)
                {
                    Component c = o as Component;
                    AttachComponent(c);
                    c.Load(reader);
                    c.Parent = this;
                }
                else
                {
                    throw new Exception("Wrong object " + o.GetType() + " found in Actor:Load");
                }
            }
            UpdateBoundingBox();
        }

        // Update bounding box location
        private void UpdateBoundingBox()
        {
            Vector3 scaledBoundingBoxSize = _boundingBoxSize * Scale;
            BoundingBox.Max = _axis.Translation + scaledBoundingBoxSize * 0.5f;
            BoundingBox.Min = _axis.Translation - scaledBoundingBoxSize * 0.5f;
        }

        // Trace
        public virtual bool Trace(Vector3 start, Vector3 end, bool traceComponents, ref float T)
        {
            Vector3 dir = end - start;
            dir.Normalize();
            float? t = BoundingBox.Intersects(new Ray(start, dir));
            if (t.HasValue)
            {
                if (traceComponents)
                {
                    bool componentsTrace = false;
                    bool foundComponentTracer = false;
                    float _T = T;
                    foreach (Component c in _components)
                    {
                        foundComponentTracer |= c.UseAsParentTracer;
                        if (c.UseAsParentTracer && c.Trace(start, end, ref _T) && _T < T)
                        {
                            T = _T;
                            componentsTrace = true;
                            //break;
                        }
                    }
                    
                    if (!foundComponentTracer && GameStartup.InEditor)
                    {
                        T = t.Value;
                        return true;
                    }
                    return (!foundComponentTracer || componentsTrace);
                }
                T = t.Value;
                return true;
            }

            return false;
        }

        // Notifies
        protected virtual void notifyAxisChanged()
        {
            foreach (Component c in _components)
            {
                c.notifyActorAxisChanged();
            }
        }

        protected virtual void notifyScaleChanged()
        {
            foreach (Component c in _components)
            {
                c.notifyActorScaleChanged();
            }
        }

        protected virtual void duplicateProperties(Actor a)
        {
            a.Axis = Axis;
            a.BoundingBox = BoundingBox;
            a.BoundingBoxSize = BoundingBoxSize;
            a.Yaw = Yaw;
            a.Pitch = Pitch;
            a.Roll = Roll;
            a.Scale = Scale;
        }

        public void DuplicateTo(Actor a)
        {
            duplicateProperties(a);
        }

        public void MapFullyLoaded(Map map)
        {
            foreach (Actor a in Childs)
            {
                a.MapFullyLoaded(map);
            }
            foreach (Component c in Components)
            {
                c.MapFullyLoaded(map);
            }
            onMapFullyLoaded(map);
        }

        protected virtual void onMapFullyLoaded(Map map)
        {
        }

        public static void DirectionToYPR(ref Vector3 dir, out float yaw, out float pitch, out float roll)
        {
            roll = 0f;
            pitch = 0f;
            yaw = 0f;

            yaw = MathHelper.ToDegrees((float)Math.Atan2(dir.Y, dir.X));
            pitch = MathHelper.ToDegrees((float)Math.Atan2(dir.Z, dir.Length()));
        }

        public static Vector3 DirectionToYPR(ref Vector3 dir)
        {
            Vector3 v = new Vector3();
            DirectionToYPR(ref dir, out v.X, out v.Y, out v.Z);
            return v;
        }

        public static void YPRToDirection(float yaw, float pitch, float roll, out Vector3 dir)
        {
            yaw = MathHelper.ToRadians(yaw);
            pitch = MathHelper.ToRadians(pitch);

            dir.X = (float)(Math.Cos(pitch) * Math.Cos(yaw));
            dir.Y = (float)Math.Sin(yaw);
            dir.Z = (float)(Math.Sin(pitch) * Math.Cos(yaw));
        }

        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("_saveable", _saveable);

            if (!_saveable)
                return;

            info.AddValue("Location", Location);
            info.AddValue("Yaw", Yaw);
            info.AddValue("Pitch", Pitch);
            info.AddValue("Roll", Roll);
            info.AddValue("Scale", Scale);

            // Serialize components
            {
                List<Component> componentsToSerialize = new List<Component>();
                foreach (Component c in Components)
                {
                    if (c.Saveable)
                    {
                        componentsToSerialize.Add(c);
                    }
                }
                info.AddValue("Components", componentsToSerialize);
            }
            
            // Serialize childs
            {
                List<Actor> childsToSerialize = new List<Actor>();
                foreach (Actor a in Childs)
                {
                    if (a.Saveable)
                    {
                        childsToSerialize.Add(a);
                    }
                }
                info.AddValue("Childs", childsToSerialize);
            }
        }
    }
}
